//
//  BCREWrapper.m
//  bcDiscriminator
//
//  Created by Volker Runkel on 03.11.06.
//  Copyright 2006 Volker Runkel, ecoObs.
//	Mail me at runkel @ eco obs . de
//
/*
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

// The following code is adopted from R.gui (Mac) which was written mainly by Simon Urbanek

#import "BCREWrapper.h"
#import <bcDiscriminatorAppController.h>

@implementation BCREWrapper

static BCREWrapper* Rwrapper=nil;

+ (BCREWrapper*) Rwrapper
{
    if (Rwrapper==nil)
        Rwrapper=[[BCREWrapper alloc] init];
    return Rwrapper;
}

- (id)init
{
	self = [super init];
	if (self) {
		char *args[5]={ "R", "--no-save", "--no-restore-data", "--gui=cocoa", 0 };
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"REngineWrapper starting up ...\n"];
		
		
		// first initialize R_LIBS if necessary
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"initialize R_LIBS ...\n"];
		BOOL flag = YES; // the default is YES for users and NO for admins
		if (flag) {
			char *cRLIBS = getenv("R_LIBS");
			NSString *addPath = [@"~/Library/R/library" stringByExpandingTildeInPath];
			if (![[NSFileManager defaultManager] fileExistsAtPath:addPath]) { // make sure the directory exists
				[[NSFileManager defaultManager] createDirectoryAtPath:[@"~/Library/R" stringByExpandingTildeInPath] attributes:nil];
				[[NSFileManager defaultManager] createDirectoryAtPath:addPath attributes:nil];
			}
			if (cRLIBS && *cRLIBS)
				addPath = [NSString stringWithFormat: @"%s:%@", cRLIBS, addPath];
			setenv("R_LIBS", [addPath UTF8String], 1);
			//NSLog(@" - setting R_LIBS=%s", [addPath UTF8String]);
		}
		
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"setting R_HOME...\n"];
		if (!getenv("R_HOME")) {
			NSBundle *rfb = [NSBundle bundleWithIdentifier:@"org.r-project.R-framework"];
			if (!rfb) {
				NSLog(@" * problem: R_HOME is not set and I can't find the framework bundle");
				if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/Frameworks/R.framework/Resources/bin/R"]) {
					NSLog(@" * I'm being desperate and I found R at /Library/Frameworks/R.framework - so I'll use it, wish me luck");
					setenv("R_HOME", "/Library/Frameworks/R.framework/Resources", 1);
				} else
					NSLog(@" * I didn't even found R framework in the default location, I'm giving up - you're on your own");
			} else {
				setenv("R_HOME", [[rfb resourcePath] UTF8String], 1);
			}
		}
		
#if (R_VERSION >= R_Version(2,2,0))
		/* 2.2.0 is now alpha, so it should be ok by now - keep this for 2.3 in case wee need it
			if (!getenv("RGUI_NOWARN_RDEVEL"))
			NSRunAlertPanel(@"Using R-devel (unstable!)",@"You are using R-devel (to become 2.2.0)\n\nSeveral properites that I rely on are changing in R-devel. I will take a wild guess as of how they change, but that guess can be completely wrong. Most importantly you have to use the default build settings. Proceed at your own risk.\n\n(Set RGUI_NOWARN_RDEVEL to get rid of this message)",NLS(@"OK"),nil,nil);
		*/
		{
			char tp[1024];
			/* since 2.2.0 those are set in the R shell script, so we need to set them as well */
			/* FIXME: possible buffer-overflow attack by over-long R_HOME */
			if (!getenv("R_INCLUDE_DIR")) {
				strcpy(tp, getenv("R_HOME")); strcat(tp, "/include"); setenv("R_INCLUDE_DIR", tp, 1);
			}
			if (!getenv("R_SHARE_DIR")) {
				strcpy(tp, getenv("R_HOME")); strcat(tp, "/share"); setenv("R_SHARE_DIR", tp, 1);
			}
			if (!getenv("R_DOC_DIR")) {
				strcpy(tp, getenv("R_HOME")); strcat(tp, "/doc"); setenv("R_DOC_DIR", tp, 1);
			}
		}
		
#if (R_VERSION >= R_Version(2,3,0))
		/* in 2.3.0 we have to set R_ARCH if appropriate */
#ifdef __ppc__
#define arch_lib_nss @"/lib/ppc"
#define arch_str "/ppc"
#else
#ifdef __i386__
#define arch_lib_nss @"/lib/i386"
#define arch_str "/i386"
#endif
#endif
#ifdef arch_lib_nss
		if (!getenv("R_ARCH")) {
			if ([[NSFileManager defaultManager] fileExistsAtPath:[[NSString stringWithUTF8String:getenv("R_HOME")] stringByAppendingString: arch_lib_nss]])
				setenv("R_ARCH", arch_str, 1);
		}
#else
#warning "Unknown architecture, R_ARCH won't be set automatically."
#endif
		
#endif /* R 2.3.0 */
#endif /* R 2.2.0 */
		
		/* setup LANG variable to match the system locale based on user's CFLocale */
#if (R_VERSION >= R_Version(2,1,0))
		//NSLog(@" - set locale");
		setenv("LANG", "en_US.UTF-8", 1);
		//NSLog(@" - ignore.system.locale is set to YES, using en_US.UTF-8");
#endif
		
		//preventReentrance = YES;
		
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"init R ...\n"];
		[[[REngine alloc] initWithHandler:self arguments:args] setCocoaHandler:nil];
		
		/* set save action */
		//[[REngine mainEngine] setSaveAction:&2]];
		[[REngine mainEngine] disableRSignalHandlers:YES];
		
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@" - initalizing R\n"];
		if (![[REngine mainEngine] activate]) {
			NSRunAlertPanel(@"Cannot start R",[NSString stringWithFormat:@"Unable to start R: %@", [[REngine mainEngine] lastError]],@"OK",nil,nil);
			exit(-1);
		}
		[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"REngineWrapper is running ...\n"];
		[(bcDiscriminatorAppController*)[NSApp delegate] setRVersion:[NSString stringWithFormat:@"Running R %s.%s",R_MAJOR,R_MINOR]];
		
		[[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] resourcePath]];
		
		if ([[REngine mainEngine] executeString:@"library(randomForest)"]) {
			NSArray *discObjects = [[NSApp delegate] discrObjects];
			BOOL success = NO;
			int i;
			for (i=0;i<[discObjects count];i++) success = [[REngine mainEngine] executeString:[NSString stringWithFormat:@"load(\"%@\")", [discObjects objectAtIndex:i]]];
			if (success)
				[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"randomForest is active ...\n"];
		}
		
		if ([[REngine mainEngine] executeString:@"library(kernlab)"]) {
			NSArray *outlObjects = [[NSApp delegate] outlObjects];
			BOOL success = NO;
			int i;
			for (i=0;i<[outlObjects count];i++) success = [[REngine mainEngine] executeString:[NSString stringWithFormat:@"load(\"%@.R\")", [outlObjects objectAtIndex:i]]];
			if (success)
				[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"kernlab is active ...\n"];
		}
	}
	return self;
}

- (void) handleWriteConsole: (NSString*) txt {
	[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:txt];
}

- (void) doProcessEvents: (BOOL) blocking {
	NSEvent *event;
	
	if (blocking){
		event = [NSApp nextEventMatchingMask:NSAnyEventMask
								   untilDate:[NSDate distantFuture]
									  inMode:NSDefaultRunLoopMode
									 dequeue:YES];
		[NSApp sendEvent:event];	
	} else {
		while( (event = [NSApp nextEventMatchingMask:NSAnyEventMask
										   untilDate:[NSDate dateWithTimeIntervalSinceNow:0.0001]
											  inMode:NSDefaultRunLoopMode 
											 dequeue:YES]))
			[NSApp sendEvent:event];
	}
	return;
}

- (void) handleProcessEvents{
	[self doProcessEvents: NO];
}

- (void)  handleShowMessage: (char*) msg
{
	NSRunAlertPanel(@"R Message",[NSString stringWithUTF8String:msg],@"OK",nil,nil);
}

- (int) handleChooseFile:(char *)buf len:(int)len isNew:(int)isNew
{
	return 0;
}

- (void) handleBusy: (BOOL) isBusy {
}

- (int) handleEditFiles: (int) nfile withNames: (char**) file titles: (char**) wtitle pager: (char*) pager
{
	return 1;
}

- (int) handleShowFiles: (int) nfile withNames: (char**) file headers: (char**) headers windowTitle: (char*) wtitle pager: (char*) pages andDelete: (BOOL) del
{
	return 1;
}

- (int) handleEdit: (char*) file
{
	return(0);
}

- (void)handleWritePrompt: (NSString*) prompt {
	[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:prompt];
	[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"\n"];
}

- (void)  handleProcessingInput: (char*) cmd
{
	NSString *s = [[NSString alloc] initWithUTF8String:cmd];
	
	[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:s];
	[(bcDiscriminatorAppController*)[NSApp delegate] writeToConsole:@"\n"];
	
	[s release];
}

- (void)setFileName:(NSString*)file
{
	[currentDataFile release];
	currentDataFile = [file copy];
	if ([[NSUserDefaults standardUserDefaults] integerForKey:@"decimalSep"] == 0)
		[[REngine mainEngine] executeString:[NSString stringWithFormat:@"data<-read.delim2(\"%@\")",currentDataFile]];
	else if ([[NSUserDefaults standardUserDefaults] integerForKey:@"decimalSep"] == 1)
		[[REngine mainEngine] executeString:[NSString stringWithFormat:@"data<-read.delim(\"%@\")",currentDataFile]];
}

- (NSArray*)outlierDetector:(NSArray*)dataRows
{
	NSString *olObj = [[NSApp delegate] currentOutlierLevel];
	//RSEXP *r;
	RSEXP *rp;
	int i;
	
	if (nil==dataRows) {
		//r = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"outl<-predict(%@,data)",olObj]];
		//r = [[REngine mainEngine] evaluateString:@"outl"];
		rp = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"outl<-predict(%@,data,type=\"prob\")",olObj]];
		rp = [[REngine mainEngine] evaluateString:@"outl"];
	}
	else {
		//r = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"outl<-predict(%@,data[c(%@),])",olObj,[dataRows componentsJoinedByString:@","]]];
		//r = [[REngine mainEngine] evaluateString:@"outl"];
		rp = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"outl<-predict(%@,data[c(%@),],type=\"prob\")",olObj,[dataRows componentsJoinedByString:@","]]];
		rp = [[REngine mainEngine] evaluateString:@"outl"];
	}

	float curOutlProb = 0.900000;
	curOutlProb = [[NSApp delegate] currentOutlObjectProb];
	//[[NSApp delegate] writeToConsole:[NSString stringWithFormat:@"Outlier prob %f for %@",curOutlProb,olObj]];
	//NSLog(@"Outlier prob is %f for %@",curOutlProb, olObj);
	int size = [rp length]/2;
	NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:size+2];
	for (i=0; i<size;i++) {
		//NSLog(@"%@ OutProbs %f",olObj, [rp doubleArray][i]);
		if ([rp doubleArray][i] >= curOutlProb )
			[tempArray addObject:[NSNumber numberWithInt:1]];
		else
			[tempArray addObject:[NSNumber numberWithInt:2]];
		//[tempArray addObject:[NSNumber numberWithInt:[r intArray][i]]];
	}
	//NSLog(@"outl results : %@",tempArray);
	
	//[r release];
	[rp release];
	
	return tempArray;
}

- (BOOL)predictFile:(NSArray*)dataRows
{
	BOOL rfObjectLoaded = YES;
	NSString *rfObj = [[NSApp delegate] currentAnalyseLevel];
	//rfObjectLoaded = [[REngine mainEngine] executeString:[NSString stringWithFormat:@"load(\"%@\")", rfObj]];
		
	/*if ([[[NSApp delegate] currentAnalyseLevel] isEqualToString:@"Genus level"]) {
		rfObjectLoaded = [[REngine mainEngine] executeString:@"load(\"genrf\")"];
		rfObj = @"genrf";
	}
	else if ([[[NSApp delegate] currentAnalyseLevel] isEqualToString:@"Myotis"]) {
		rfObjectLoaded = [[REngine mainEngine] executeString:@"load(\"myorf\")"];
		rfObj = @"myorf";
	}
	else if ([[[NSApp delegate] currentAnalyseLevel] isEqualToString:@"Nyctaloid"]) {
		rfObjectLoaded = [[REngine mainEngine] executeString:@"load(\"nycrf\")"];
		rfObj = @"nycrf";
	}
	else if ([[[NSApp delegate] currentAnalyseLevel] isEqualToString:@"Pipistrelloid"]) {
		rfObjectLoaded = [[REngine mainEngine] executeString:@"load(\"piprf\")"];
		rfObj = @"piprf";
	}*/
	
	if (rfObjectLoaded) {
		RSEXP *r;
		if (nil==dataRows) 
			r = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"pred<-predict(%@,data, type=\"prob\")",rfObj]];
		else {
			r = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"pred<-predict(%@,data[c(%@),], type=\"prob\")",rfObj,[dataRows componentsJoinedByString:@","]]];
		}
		
		double *res = [r doubleArray];
		int size = [r length];
		
		NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:size+2];
		
		int cols,rows;
		cols = [[[REngine mainEngine] evaluateString:@"ncol(pred)"] integer];
		rows = [[[REngine mainEngine] evaluateString:@"nrow(pred)"] integer];
		
		[tempArray addObject:[NSNumber numberWithInt:cols]];
		[tempArray addObject:[NSNumber numberWithInt:rows]];	
		int i,j;
		for (i=0;i<rows;i++) {
			for (j=0;j<cols;j++) {
				[tempArray addObject:[NSNumber numberWithFloat:res[i+j*rows]]];
			}
		}
		
		[[NSApp delegate] setProbsArray:tempArray];
		// Now we have the probs in our tempArray pos 0 and 1 hold col# / row#
		
		[r release];
		r = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"%@$classes",rfObj]];
		[[NSApp delegate] setClassArray:[r array]];
		
		[tempArray release];
	}
	
	return rfObjectLoaded;
}

@end
